#include "testsgenerator.h"

/* Constructores*/
TestsGenerator::TestsGenerator()
{
    n_pruebas= m_operaciones= k_nodos= 0;
    GENTESTFILE= false;

    minrandKey= 1;
    maxrandKey= 1000;

    nopHeap= 2;
    nopFHeap= -1;

    probDecr= 0;
    probExt= 0;

    srand(time(NULL));
}

TestsGenerator::TestsGenerator(long n, long m, long k, long gentestfile, int minrand, int maxrand)
{
    n_pruebas= n;
    m_operaciones= m;
    k_nodos= k;
    GENTESTFILE= gentestfile;

    minrandKey= minrand;
    maxrandKey= maxrand;

    probDecr= 0;
    probExt= 0;

    nopHeap= 2;
    nopFHeap= -1;

    srand(time(NULL));
}

TestsGenerator::TestsGenerator(long n, long m, long k, long gentestfile)
{
    n_pruebas= n;
    m_operaciones= m;
    k_nodos= k;
    GENTESTFILE= gentestfile;

    minrandKey= 1;
    maxrandKey= 1000;

    probDecr= 0;
    probExt= 0;

    nopHeap= 2;
    nopFHeap= -1;

    srand(time(NULL));
}

/* Funciones de configuracion de la clase */

void TestsGenerator::setNPruebas (long n)
{
    n_pruebas= n;
}

void TestsGenerator::setMOperaciones (long m)
{
    m_operaciones= m;
}

void TestsGenerator::setKNodos (long k)
{
    k_nodos= k;
}

void TestsGenerator::setGenTestFile(bool value)
{
    GENTESTFILE= value;
}

void TestsGenerator::setnOpHeap(int n)
{
    nopHeap= n;
}

void TestsGenerator::setnOpFHeap(int n)
{
    nopFHeap= n;
}

/* Funciones para generar claves e indices aleatorios */
void TestsGenerator::setRandLimits (int min, int max)
{
    minrandKey= min;
    maxrandKey= max;
}

int TestsGenerator::getRandNumber ()
{
    int output= rand() % (maxrandKey - minrandKey) + 1;

    output+= minrandKey;

    return (output);
}

int TestsGenerator::getRandNumber (int min, int max)
{
    int output= rand() % (max - min) + 1;

    output= 5000; //+= min;

    return (output);
}

int TestsGenerator::getRandIndex ()
{
    int output= rand() % (k_nodos) + 1;

    return (output);
}

/* Funciones para la generacion de los heaps */
Heap* TestsGenerator::genHeap()
{
    Heap* aux_heap= new Heap (k_nodos+1);

    // Ahora llenemos el heap
    for (long i=0; i<k_nodos; i++)
        aux_heap->Insert(getRandNumber());

    // Nos quedamos con el apuntador al heap creado
    if (heap!=NULL)
        delete heap;

    heap= aux_heap;

    //printf("Heap generado correctamente.\n");

    return heap;
}

fib_heap* TestsGenerator::genFHeap()
{
    delete fheap; // Para evitar que las pruebas tomen 2 GB de memoria
    fib_heap* aux_fheap= new fib_heap ();

    // Ahora llenemos el heap
    for (long i=0; i<k_nodos; i++)
        aux_fheap->Insert(k_nodos-i);

    // Nos quedamos con el apuntador al heap creado
    if (fheap!=NULL)
        delete heap;

    fheap= aux_fheap;

    //printf("Heap de Fibonacci generado correctamente.\n");

    return fheap;
}

void TestsGenerator::printHeap(bool all)
{
    if (heap!=NULL)
        heap->Impr(all);
}

void TestsGenerator::printFHeap()
{
    /*if (fheap!=NULL)
        fheap->Impr();*/
}

/* Funciones para la generacion de pruebas */
int TestsGenerator::genHeapOperationProb()
{
    int taken=0; // Probabilidad ya tomada por las demas operaciones

    int prob= rand() % (100 - taken) + 1;
    HeapOperationsProb[0]= probExt * prob; // Probabilidad de "extraerMin" con un peso de 0.9

    taken+= prob; // Las demas operaciones tendran la prob restante

    HeapOperationsProb[1]= probDecr * (100 - taken); // La ultima operacion
    // tendra la probabilidad restante, con un peso de 0.1

    int mayor= 0;
    int operindex= 0;

    for (int i=0; i<nopHeap; i++)
    {
        int test= HeapOperationsProb[i];

        if (test> mayor)
        {
            mayor= test;
            operindex= i;
        }
    }

    return operindex;
}

int TestsGenerator::genFHeapOperationProb()
{
    int taken=0; // Probabilidad ya tomada por las demas operaciones

    int prob= rand() % (100 - taken) + 1;
    FHeapOperationsProb[0]= probExt * prob; // Probabilidad de "extraerMin" con un peso de 0.9

    taken+= prob; // Las demas operaciones tendran la prob restante

    FHeapOperationsProb[1]= probDecr * (100 - taken); // La ultima operacion
    // tendra la probabilidad restante, con un peso de 0.1

    int mayor= 0;
    int operindex= 0;

    for (int i=0; i<nopFHeap; i++)
    {
        int test= FHeapOperationsProb[i];

        if (test> mayor)
        {
            mayor= test;
            operindex= i;
        }
    }

    return operindex;
}

/* Funciones de ejecucion de pruebas */
bool TestsGenerator::runHeapTest()
{
    printf("\nEjecutando pruebas con el Heap...\n\n");

    bool OK= false;

    struct timeval t_ini, t_fin; // Las siguientes 3 lineas permiten medir el tiempo de ejecucion del calculo de pi
    double secs;

    double t_max=-1, t_min=99999999, t_avg=0;

    for (int n= 0; n<n_pruebas; n++)
    {
        // Primero vamos a reestablecer nuestro heap
        genHeap();

        // Y determinemos las probabilidades por operacion,
        // esto se hara cada 4 pruebas
        if (n%4==0)
        {
            printf("\nProbabilidad de ExtraerMin [0-100]: ");
            scanf("%d", &probExt);

            printf("Probabilidad de DecrClave [0-100]: ");
            scanf("%d", &probDecr);
        }

        gettimeofday(&t_ini, NULL); // Aca tomamos el tiempo actual

        for (int m= 0; m<m_operaciones; m++)
        {
            /* Finalmente, ejecutamos la operacion,
             * esta se determina a traves de un random por cada una,
             * siendo la operacion final aquella que obtenga el
             * numero mayor. Las posibilidades tienen un peso para
             * propiciar unas sobre otras. */
            execHeapOp(genHeapOperationProb());
        }

        gettimeofday(&t_fin, NULL); // De nuevo tomamos el tiempo actual
        secs = timeval_diff(&t_fin, &t_ini); // Aca la diferencia de ambos tiempos devuelta en segundos

        double t_total_prueba= secs * 1000.0;

        if (t_total_prueba>t_max) // Tiempo maximo?
            t_max= t_total_prueba;

        if (t_total_prueba<t_min) // Tiempo minimo?
            t_min= t_total_prueba;

        t_avg+= t_total_prueba; // Para el tiempo promedio

        printf("Tiempo total prueba %d: %.16g ms\n", n, t_total_prueba); // Imprimimos el tiempo con 16 digitos de precision
    }

    t_avg= t_avg / n_pruebas;

    /* Reportamos los tiempos */
    printf("\nTiempo Maximo: %.16g\n", t_max);
    printf("Tiempo Promedio: %.16g\n", t_avg);
    printf("Tiempo Minimo: %.16g\n\n", t_min);

    return OK;
}

bool TestsGenerator::runFHeapTest()
{
    printf("\nEjecutando pruebas con el Heap de Fibonacci...\n\n");

    bool OK= false;

    struct timeval t_ini, t_fin; // Las siguientes 3 lineas permiten medir el tiempo de ejecucion del calculo de pi
    double secs;

    double t_max=-1, t_min=99999999, t_avg=0;

    for (int n= 0; n<4; n++)
    {
        // Primero vamos a reestablecer nuestro heap
        genFHeap();

        // Y determinemos las probabilidades por operacion,
        // esto se hara cada 4 pruebas
        if (n%4==0)
        {
            printf("\nProbabilidad de ExtraerMin [0-100]: ");
            scanf("%d", &probExt);

            printf("Probabilidad de DecrClave [0-100]: ");
            scanf("%d", &probDecr);
        }

        gettimeofday(&t_ini, NULL); // Aca tomamos el tiempo actual

        double tiempo_resta_total=0;

        for (int m= 0; m<m_operaciones; m++)
        {
            /* Finalmente, ejecutamos la operacion,
             * esta se determina a traves de un random por cada una,
             * siendo la operacion final aquella que obtenga el
             * numero mayor. Las posibilidades tienen un peso para
             * propiciar unas sobre otras. */

            double tiempo_resta=0; // Este tiempo es un overhead generado por un vector
            // que se utiliza para el decrementar clave
            execFHeapOp(genFHeapOperationProb(), tiempo_resta);
            tiempo_resta_total+= tiempo_resta;

        }

        gettimeofday(&t_fin, NULL); // De nuevo tomamos el tiempo actual
        secs = timeval_diff(&t_fin, &t_ini); // Aca la diferencia de ambos tiempos devuelta en segundos

        double t_total_prueba= secs * 1000.0;

        //printf("Tiempo resta total: %.16g\n", tiempo_resta_total);

        t_total_prueba-= tiempo_resta_total; // Restamos el overhead ajeno a la prueba

        if (t_total_prueba>t_max) // Tiempo maximo?
            t_max= t_total_prueba;

        if (t_total_prueba<t_min) // Tiempo minimo?
            t_min= t_total_prueba;

        t_avg+= t_total_prueba; // Para el tiempo promedio

        printf("Tiempo total prueba %d: %.16g ms\n", n, t_total_prueba); // Imprimimos el tiempo con 16 digitos de precision
    }

    t_avg= t_avg / 1;

    /* Reportamos los tiempos */
/*    printf("\nTiempo Maximo: %.16g\n", t_max);
    printf("Tiempo Promedio: %.16g\n", t_avg);
    printf("Tiempo Minimo: %.16g\n\n", t_min);
*/
    return OK;
}

/* Aca se definen aleatoriamente las operaciones que vamos a realizar en el heap */
void TestsGenerator::execHeapOp(int operIndex)
{
    //printf("Ejecutando %d\n", operIndex);
    switch (operIndex)
    {
        case 0: heap->Delete(1); // O eliminamos el menor
                break;
        case 1: heap->Decrease_Key(1, 1); // O decrementamos una clave
                break;
        default: break;
    }
}

void TestsGenerator::execFHeapOp(int operIndex, double &t_resta)
{
    switch (operIndex)
    {
        case 0: {
                    fheap->Extract_Min(); // O eliminamos el menor
                    t_resta= 0;
                    break;
                }
        case 1:
                {
                    struct timeval t_ini, t_fin; // Las siguientes 3 lineas permiten medir el tiempo de ejecucion del calculo de pi
                    double secs;

                    gettimeofday(&t_ini, NULL); // Aca tomamos el tiempo actual

                    //int index= getRandIndex(); // Por alguna razon, las operaciones random terminan en el cierre de la
                    // aplicacion
                    int index= 1;
                    vector <Node*>::iterator i;
                    i= fheap->NodeArray.begin()+index;

                    Node* aux1= fheap->NodeArray.at(index);

                    if (aux1!=NULL)
                    {
                        fheap->Decrease_Key(aux1, fheap->Get_Min()->key-1); // O decrementamos una clave
                        fheap->NodeArray.push_back(aux1);
                        fheap->NodeArray.erase(i);
                    }

                    gettimeofday(&t_fin, NULL); // De nuevo tomamos el tiempo actual
                    secs = timeval_diff(&t_fin, &t_ini); // Aca la diferencia de ambos tiempos devuelta en segundos

                    t_resta= secs * 1000.0;

                    break;
                }
        default: break;
    }
}

/* Funciones para medir el tiempo de ejecucion */
double TestsGenerator::timeval_diff (struct timeval *a, struct timeval *b)
{
    return
    (double)(a->tv_sec + (double)a->tv_usec/1000000) -
    (double)(b->tv_sec + (double)b->tv_usec/1000000);
}
